#!/bin/bash

# iplib - Bash ip functions
# Hessel Schut, hessel@isquared.nl, 2011-03-29

k_max_ttl=30	# max ttl value for traceroute

function split_dotquad() {
	if [ ! -z "${1}" ]; then
		echo ${1}
	else
		cat
	fi | while read quad; do 
		octets=${quad//./ };
		echo ${octets};
	done
}

function join_octets() { 
	dotquad="";
	if [ ${#} -eq 4 ]; then 
		octets=${*}; 
	else 
		read octets; 
	fi; 
	for octet in ${octets}; do
		dotquad=${dotquad}${dotquad:+.}${octet};
	done
	echo ${dotquad};
}

function dotquad2int() {
	read o3 o2 o1 o0 <<< $(split_dotquad ${1})
	echo $((2**24 * o3 + 2**16 * o2 + 2**8 * o1 + o0))
}

function int2dotquad() { 
	echo	$(((${1} >> 24) & 255)) \
		$(((${1} >> 16) & 255)) \
		$(((${1} >>  8) & 255)) \
		$((${1} & 255)) | join_octets
}

alias inet_aton=dotquad2int
alias inet_ntoa=int2dotquad

function rnd_ip() {
	for octet in {0..3}; do
		echo -n "$((RANDOM % 255)) ";
	done | join_octets
}

function ping_compat() {
	case ${OSTYPE} in
		'linux-gnu')
			ttl_switch=-t
			gateway_field=2
			target_field=4
		;;
		'darwin10.0'|'darwin11'|'darwin12')
			ttl_switch=-m
			gateway_field=4
			target_field=4
		;;
		*)
			echo 'ERROR: OS not supported yet'
			exit
		;;
	esac
	case $1 in
		'ttl_sw')
			echo ${ttl_switch};
		;;
		'gw_field')
			echo ${gateway_field}
		;;
		'target_field')
			echo ${target_field}
		;;
	esac
}
alias ping_osver=ping_compat

function f_trace() {
        [ ${2} -gt ${k_max_ttl} ] && return 1
        read host status <<< $(ping $(ping_compat ttl_sw) ${2} -n -c 1 ${1} | \
		awk 	-v target=$(ping_compat target_field) \
			-v gw=$(ping_compat gw_field) '
                		/exceeded/ {
					gsub(/:/,"",$(gw));
                        		print $(gw), "TTL_EXCEEDED"
                		}
                		/bytes from/ {
                        		gsub(/:/,"",$(target));
                        		print $(target), "TRACE_OK"
               			 }
				/timeout/ {
					print "0.0.0.0", "NO_ANSWER"
				}
			')
        echo ${2} ${host} # ${status}
        [ "${status}" == "TRACE_OK" ] && return 0
        f_trace ${1} $((${2} + 1));
}

function trace() { f_trace ${1} 1; }

function httptrace() {		
	exec 3<>/dev/tcp/$(cut -d/ -f3 <<< ${1})/80 && (		
		echo -ne "HEAD /$(cut -d/ -f4- <<< ${1}) HTTP/1.1\r\n" >&3
		echo -ne "Host: $(cut -d/ -f3 <<< ${1})\r\n" >&3		
		echo -ne "User-Agent: HttpTrace/0.5\r\n" >&3		
		echo -ne "Connection: close\r\n\r\n" >&3 && cat <&3		
	) | {		
		s=0; u=""		
		read r && s=$(cut -d\  -f2 <<< ${r})		
		while read r; do		
			r=$(tr -d \\r <<< ${r})		
			[[ "${s}" -ge 300 ]] && [[ "${s}" -lt 400 ]] && {		
				[[ "${r}" =~ "Location: " ]] && {		
					u=$(cut -d\  -f2- <<< ${r})		
				}		
			}		
			[[ "${r}" =~ "Server: " ]] && {		
				v=$(cut -d\  -f2- <<< ${r})		
			}		
		done		
		echo "${s}: ${u:-${1}} (${v})"		
		[[ -z "${u}" ]] || httptrace "${u}"		
		exit		
	}		
	1<&3-;		
}

function filter_bogon() {
	if [ -z "${1}" ]; then
                cat
        else
                echo ${1}
        fi | while read ip
	do
		octet=0; bogon=0
		for value in $(split_dotquad ${ip}); do
			
			case ${octet} in 
				0)
					if [ ${value} -ge 224 ]
					then
						# RFC3171 multicast / broadcast
						bogon=1; break
					fi
	
					case ${value} in 
						0|10|127)
							# RFC1700 / RFC1918
							bogon=1; break
						;;
						
						169|172|192|198|203)
							# process next octet for these
							o0=${value}
						;;
					esac
				;;
	
				1)
					case ${o0} in
						169)
							if [ ${value} -eq 254 ]; then
								# IPv4 autoconfiguration
								bogon=1; break
							fi
						;;
	
						172)
							if [ ${value} -ge 16 ] && [ ${value} -le 31 ]; then
								# RFC1918 (172.16.0.0/12)
								bogon=1; break
							fi
						;;
	
						192)
							case ${value} in
								2|88)
									# process next octet
									o1=${value}
								;;
								168)
									# RFC1918 (192.168.0.0/16)
									bogon=1; break
								;;
							esac
						;;
	
						198)	
							case ${value} in 
								18|19)
									# RFC2544
									bogon=1; break
								;;
						
								51)
									o2=${value}
								;;
							esac
						;;
						
						203)
							if [ ${value} -eq 0 ]; then
								o2=${value}
							fi
						;;
					esac
				;;
	
				2)
					case ${o1} in
						0)
							case ${value} in
								2|113)
									# TEST-NET-{1,3}
									bogon=1; break
								;;
							esac
						;;
	
						51)
							if [ ${value} -eq 100 ]; then
								# TEST-NET-2
								bogon=1; break
							fi
						;;
	
						88)
							if [ ${value} -eq 99 ]; then
								# RFC3068 6to4 anycast
								bogon=1; break
							fi
						;;
					esac
				;;
			esac
	
			((octet++))	
		done
		if [ ${bogon} -eq 0 ]; then
			echo ${ip}
		fi
	done
}
